package concurrent.c_012_ThreadPool;

import lombok.AllArgsConstructor;
import util.TimeUtil;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.*;

/**
 * FutureTask 是接口 RunnableFuture 的唯一实现类（实现了 Future + Runnable）
 * 1.Runnable 接口，可开启单个线程执行。
 * 2.Future 接口，可接受 Callable 接口的返回值，futureTask.get() 阻塞获取结果。
 * FutureTask 弥补了 Future 必须用线程池提交返回 Future 的缺陷
 * FutureTask 是 Thread.start() 就开始执行了。而结果是 get() 的时候才返回（如果提前完成，结果也会先缓存在对象内。否则 get 就会阻塞直到有结果）
 * 注意：若任务里抛出了异常。那么 get 方法就会报错从而中断主线程（相当于没有要返回值的异步执行），但是，如果不调用 get 方法，主线程是不会中断的。
 */
public class T07_FutureTask {

    static ExecutorService exs = Executors.newFixedThreadPool(10);

    public static void main(String[] args) {
        demo2();
    }

    /**
     * 一个开启线程执行任务，一个阻塞等待执行结果，分离这两步骤，可在这两步中间穿插别的相关业务逻辑
     * 如下，分离之后，FutureTaskThread 耗时 10 秒期间，主线程还穿插执行了耗时 5 秒的操作。且可根据业务逻辑实时判断是否需要继续执行 FutureTask
     * 在特定场合，例如有十分耗时的业务但有依赖于其他业务不一定非要执行的，可以尝试使用
     */
    public static void demo1() {
        try {
            System.out.println("===== 统计公司总部和分部的总利润是否达标 100 万 ==========");
            // 记录总公司的利润综合
            int count = 0;
            // 1.定义一个 FutureTask，假设去远程 http 获取各个分公司业绩（任务都比较耗时）
            FutureTask<Integer> futureTask = new FutureTask<>(new Demo1CallableTask());
            Thread futureTaskThread = new Thread(futureTask);
            futureTaskThread.start();
            System.out.println("futureTaskThread start！" + TimeUtil.nowDateTime());

            // 2.主线程先做点别的事
            System.out.println("主线程查询总部公司利润开始时间：" + TimeUtil.nowDateTime());
            Thread.sleep(5000);
            count += 10; // 10 表示北京集团总部利润
            System.out.println("主线程查询总部公司利润结果时间：" + TimeUtil.nowDateTime());


            if (count >= 100) { // 总部已达标 100 万利润，就不再继续执行获取分公司业绩任务了
                System.out.println("总部公司利润达标，取消 futureTask！" + TimeUtil.nowDateTime());
                futureTask.cancel(true); // 不需要再去获取结果，那么直接取消即可
            } else {
                System.out.println("总部公司利润未达标，进入阻塞查询分公司利润！" + TimeUtil.nowDateTime());

                // 3.总部未达标，阻塞获取各个分公司结果，然后分别去获取分公司的利润
                Integer i = futureTask.get(); // 真正执行 CallableTask
                System.out.println("获取到公司利润结果 i = " + i + " " + TimeUtil.nowDateTime());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 模拟一个十分耗时的任务：去所有的分公司里去获取利润结果
    static class Demo1CallableTask implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            System.out.println("CallableTask-call，查询分公司利润，执行开始！" + TimeUtil.nowDateTime());
            Thread.sleep(10000);
            System.out.println("CallableTask-call，查询分公司利润，执行完毕！" + TimeUtil.nowDateTime());
            return 10;
        }
    }

    /**
     * FutureTask 一样可以并发执行任务并获取结果，这里多套一层 FutureTask 比较鸡肋（直接返回 Future 简单明了）不建议使用
     */
    public static void demo2() {
        long start = System.currentTimeMillis();
        // 结果集
        List<Integer> list = new ArrayList<>();
        List<FutureTask<Integer>> futureList = new ArrayList<>();
        try {
            // 启动线程池，和 T06_Futrue 对比，只有这块有点不一样
            for (int i = 0; i < 10; i++) {
                FutureTask<Integer> futureTask = new FutureTask<>(new Demo2CallableTask(i + 1));
                // 提交任务，添加返回，Runnable 特性
                exs.submit(futureTask);
                // Future 特性，提交任务后把 futureTask 添加进 futureList
                futureList.add(futureTask);
            }

            long getResultStart = System.currentTimeMillis();
            System.out.println("结果归集开始时间 = " + TimeUtil.nowDateTime());

            while (futureList.size() > 0) { // 结果归集
                Iterator<FutureTask<Integer>> iterable = futureList.iterator();
                while (iterable.hasNext()) { // 遍历一遍
                    Future<Integer> future = iterable.next();
                    if (future.isDone() && !future.isCancelled()) {
                        Integer i = future.get(); // Future 特性，阻塞
                        list.add(i);
                        iterable.remove(); // 任务完成移除任务
                        System.out.println("任务 i = " + i + " 获取完成，移出任务队列！" + TimeUtil.nowDateTime());
                    } else {
                        Thread.sleep(1); // 避免 CPU 高速轮循
                    }
                }
            }

            System.out.println("任务处理结果 list = " + list);
            System.out.println("总耗时 = " + (System.currentTimeMillis() - start) + "，取结果归集耗时 = " + (System.currentTimeMillis() - getResultStart));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            exs.shutdown();
        }
    }

    @AllArgsConstructor
    static class Demo2CallableTask implements Callable<Integer> {
        Integer i;

        @Override
        public Integer call() throws Exception {
            if (i == 1) {
                Thread.sleep(3000); // 任务 1 耗时 3 秒
            } else if (i == 5) {
                Thread.sleep(5000); // 任务 5 耗时 5 秒
            } else {
                Thread.sleep(1000); // 其它任务耗时 1 秒
            }
            System.out.println("task 线程：" + Thread.currentThread().getName() + "任务 i = " + i + "，完成！" + TimeUtil.nowDateTime());
            return i;
        }
    }
}
